package com.tool.grab.vpn.header;

/**
 *  传输层
 *  TCP协议报文格式
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜T
 * ｜                    16 bit                         ｜                     16 bit                        ｜C
 * ｜             源端口 （local port）                 ｜         目的端口（remote port）                   ｜P
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜报
 * ｜                                                 32 bit                                                 ｜文
 * ｜                                        序列码（sequence number）                                       ｜首
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜部
 * ｜                                                 32 bit                                                 ｜默
 * ｜                               确认序列码（acknowledgement sequence number）                            ｜认
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜长
 * ｜          8 bit         ｜ 8 bit ｜U｜A｜P｜R｜S｜F｜                   16 bit                          ｜为
 * ｜          头部长        ｜ 标志位｜R｜C｜S｜S｜Y｜I｜                  窗口大小                         ｜2
 * ｜   前4位（单位 4 byte） ｜ 后6位 ｜G｜K｜H｜T｜N｜N｜                 windows size                      ｜0
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜个
 * ｜                      16 bit                       ｜                   16 bit                          ｜字
 * ｜                校验和（check sum）                ｜                  紧急指针                         ｜节
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * ｜                                    选项和填充（最小1 byte  最大 40 byte）                              ｜
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * ｜                                                 数据                                                   ｜
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * 选项和填充最小为 1 byte ，若没有选项、这个字节中所有位填充为0；
 * 最大为40 byte 因为8 bit 头部长度能表达的最大数为60 byte，减去头部默认长度20byte，剩40byte
 *
 */
public class TCPPacket extends Packet{
    private static final String TAG = TCPPacket.class.getSimpleName();

    static final int LOCAL_PORT_BIT = 0;
    static final int REMOTE_PORT_BIT = 2;
    static final int SEQUENCE_BIT = 4;
    static final int ACKNOWLEDGEMENT_SEQUENCE_BIT = 8;
    static final int HEADER_LENGTH_BIT = 12;
    static final int TAG_BIT = 13;
    static final int WINDOWS_SIZE_BIT = 14;
    static final int CHECK_SUM_BIT = 16;
    static final int URG_BIT = 18;

    public TCPPacket(byte[] bytes , int... parameters){
        super(bytes,parameters);
    }

    /**
     * 获取源端口
     * 源端口
     * 在TCP数据报中第0、1字节 占2字节 共16位
     * @return
     */
    public int getLocalPort(){
       return bytes[LOCAL_PORT_BIT + offset] & 0xFF << 8  | bytes[LOCAL_PORT_BIT + offset + 1] & 0xFF;
    }

    /**
     * 设置本地锻端口
     * @return
     */
    public void setLocalPort(int port){
        bytes[LOCAL_PORT_BIT + offset] = (byte) (port >> 8);
        bytes[LOCAL_PORT_BIT + offset + 1] = (byte) port;
    }

    /**
     * 获取目的端口
     * 目的端口
     * 在TCP数据报中第2、3字节 占2字节 共16位
     * @return
     */
    public int getRemotePort(){
        return bytes[REMOTE_PORT_BIT + offset] & 0xFF << 8  | bytes[REMOTE_PORT_BIT + offset + 1] & 0xFF;
    }

    /**
     * 设置远程端口
     * @param port
     */
    public void setRemotePort(int port){
         bytes[REMOTE_PORT_BIT + offset] = (byte) (port >> 8);
         bytes[REMOTE_PORT_BIT + offset + 1] = (byte) port;
    }

    /**
     * 获取序列码
     *
     *  序列码
     *      在TCP数据报中第4、5、6、7字节 占4字节 共32位
     *      由初始请求方随机生成，用来标识数据起始位置的编码。
     *      TCP将应用层发来的数据对每一字节顺序编号
     *      TCP首部中的序列号是指在本段报文段所携带数据的第一个字节编号
     *      应答方收到请求后值+1后 作为确认序列码返回给请求方标识该请求被成功送达
     *      序列码和标志位配合使用,以实现不同的状态逻辑
     */
    public int getSequence(){
        return    bytes[SEQUENCE_BIT + offset] & 0xFF << 24
                | bytes[SEQUENCE_BIT + offset + 1] & 0xFF << 16
                | bytes[SEQUENCE_BIT + offset + 2] & 0xFF << 8
                | bytes[SEQUENCE_BIT + offset + 3] & 0xFF;
    }

    /**
     * 获取确认码
     *
     *  确认序列码
     *      在TCP数据报中第8、9、10、11字节 占4字节 共32位
     *      如果是请求方 则确认码就是序列码
     *      如果是应答方 则确认码是收到的确认码+1
     * @return
     */
    public int getAcknowledgementSequence(){
        return bytes[ACKNOWLEDGEMENT_SEQUENCE_BIT + offset] & 0xFF << 24
                | bytes[ACKNOWLEDGEMENT_SEQUENCE_BIT + offset + 1] & 0xFF << 16
                | bytes[ACKNOWLEDGEMENT_SEQUENCE_BIT + offset + 2] & 0xFF << 8
                | bytes[ACKNOWLEDGEMENT_SEQUENCE_BIT + offset + 3] & 0xFF;
    }

    /**
     * 获取首部长
     * 头部长
     *      在TCP数据报中第12字节前4位 占1/2字节 共4位 单位 4 byte
     *      TCP默认报头长20字节
     * @return
     */
    public int getHeaderLength(){
        return (bytes[HEADER_LENGTH_BIT + offset] >> 4 & 0xF) * 4;
    }

    /**
     * 获取标志位
     *
     * 标志位
     *      在TCP数据报中第13字节后6位 占2/3字节 共6位
     *      从第13字节第3位起 左至右依次表示为 URG(urgent) ACK(acknowledge) PSH（push） RST(reset) SYN(synchronous) FIN(fin)
     *      URG 紧急标志 说明这个报文中包含需紧急处理的数据 该位值为 1 时紧急指针生效 指向需紧急处理的数据
     *      ACK 确认标志 说明发送的请求被送达并返回响应
     *      PSH 推标志   该标志置位时，接收端不将该数据进行队列处理，而是尽可能快地将数据转由应用处理。在处理Telnet或r login等交互模式的连接时，该标志总是置位的。
     *      RST 复位标志 要求复位相应的TCP连接
     *      SYN 同步标志 该标志位只在3次握手建立TCP连接时有效 提示TCP连接的服务端检查序列编号
     *      FIN 结束标志 该标志位只在4次挥手断开连接时有效
     */
    public int getTag(){
        return bytes[TAG_BIT + offset] & 0x2F;
    }

    public boolean isURG(){
        return getTag() >> 5 == 1;
    }

    public boolean isACK(){
        return (getTag() >> 4 & 1) == 1;
    }

    public boolean isPSH(){
        return (getTag() >> 3 & 1) == 1;
    }
    public boolean isRST(){
        return (getTag() >> 2 & 1) == 1;
    }

    public boolean isSYN(){
        return (getTag() >> 1 & 1) == 1;
    }

    public boolean isFIN(){
        return (getTag() & 1) == 1;
    }

    /**
     * 获取窗口大小
     * 窗口大小
     *      在TCP数据报中第14、15字节 占2字节 共16位
     *      用来表示想收到的每个TCP数据段的大小。TCP的流量控制由连接的每一端通过声明的窗口大小来提供
     *      窗口大小最大为65535字节
     * @return
     */
    public int getWindowsSize(){
       return bytes[WINDOWS_SIZE_BIT + offset] & 0xFF << 8 | bytes[WINDOWS_SIZE_BIT + offset + 1] & 0xFF;
    }

    /**
     * 获取校验和
     * 校验和
     *      在TCP数据报中第16、17字节 占2字节 共16位
     *      用于对TCP数据的校验  由发送端对数据报中所有数据计算得出
     * @return
     */
    public int getCheckSum(){
        return bytes[CHECK_SUM_BIT + offset] << 8 | bytes[CHECK_SUM_BIT + offset + 1] ;
    }

    /**
     * 检查校验和
     * @return
     */
    public boolean checkSum(){
        return  (short) ~getSum() == 0;
    }

    private int getSum(){
        int m = 0;

        for(int i = offset;i < validLength;i += 2){
            m += bytes[i] << 8 | bytes[i + 1];
        }

        if((validLength - offset) % 2 > 0){
            m += bytes[validLength - 1] << 8;
        }

        while ((m >> 16) > 0){
            m = (m >> 16) + (m & 0xFFFF);
        }

        return m;
    }

    public void refreshCheckSum(){
        bytes[CHECK_SUM_BIT + offset] = 0;
        bytes[CHECK_SUM_BIT + offset + 1] = 0;
        int m = getSum();
        bytes[CHECK_SUM_BIT + offset] = (byte) (~m >> 8);
        bytes[CHECK_SUM_BIT + offset + 1] = (byte) ~m;
    }

    /**
     * 获取紧急指针
     * 紧急指针
     *      在TCP数据报中第18、19字节 占2字节 共16位
     *      用于标识数据报中需紧急处理的数据位置
     * @return
     */
    public int getUrg(){
       return bytes[URG_BIT + offset] & 0xFF << 8 | bytes[URG_BIT + offset + 1] & 0xFF;
    }

    /**
     * TCP数据报中
     * 数据长度 = 窗口大小 - 首部长
     * 数据起始位 = 首部长度 + 数据偏移（如果有）
     * 结束位 = 数据起始位 + 数据长度
     * @return
     */
    public byte[] getData(){
        int headerLength =  getHeaderLength();
        int start = headerLength + offset;
        int size = getWindowsSize() - headerLength;

        byte[] data = new byte[size];

        for(int i = 0; i < data.length; i++){
            if(i + start >= bytes.length) continue;
            data[i] = bytes[start + i];
        }
        return data;
    }







}
